﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace ArcSoftFaceTool
{
    public class ArcSoftFace
    {
        

        static IntPtr pImageEngine = IntPtr.Zero;
        public static int ActivationRetCode = 0;
        static ArcSoftFace()
        {
            
            string AppId = ConfigurationSettings.AppSettings["ArcSoftAPPID"] ?? "59BCFKXVCtRkRYa8d4QjWkgVAF9AieGhq1XhWztnNuJk";
            string SdkKey = ConfigurationSettings.AppSettings["ArcSoftSDKKEY"] ?? "CQkBiCairzYJbMJyMnP96h1LmtrGX4W6mb6Ps32gUq3c";
            ASFActivation(AppId, SdkKey);
            ActivationRetCode = ArcSoftFace.InitEngine();
            
        }

        /// <summary>
        /// 初始化引擎
        /// </summary>
        /// <param name="detectMode">AF_DETECT_MODE_VIDEO 视频模式 | AF_DETECT_MODE_IMAGE 图片模式</param>
        /// <param name="detectFaceOrientPriority">检测脸部的角度优先值，推荐：ASF_OrientPriority.ASF_OP_0_HIGHER_EXT</param>
        /// <param name="detectFaceScaleVal">用于数值化表示的最小人脸尺寸</param>
        /// <param name="detectFaceMaxNum">最大需要检测的人脸个数</param>
        /// <param name="combinedMask">用户选择需要检测的功能组合，可单个或多个</param>
        /// <param name="pEngine">初始化返回的引擎handle</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFInitEngine(uint detectMode, int detectFaceOrientPriority, int detectFaceScaleVal, int detectFaceMaxNum, int combinedMask, ref IntPtr pEngine);

        /// <summary>
        /// 人脸信息检测（年龄/性别/人脸3D角度）
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <param name="width">图像宽度</param>
        /// <param name="height">图像高度</param>
        /// <param name="format">图像颜色空间</param>
        /// <param name="imgData">图像数据</param>
        /// <param name="detectedFaces">人脸信息，用户根据待检测的功能裁减选择需要使用的人脸</param>
        /// <param name="combinedMask">只支持初始化时候指定需要检测的功能，在process时进一步在这个已经指定的功能集中继续筛选例如初始化的时候指定检测年龄和性别， 在process的时候可以只检测年龄， 但是不能检测除年龄和性别之外的功能</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFProcess(IntPtr pEngine, int width, int height, int format, IntPtr imgData, IntPtr detectedFaces, int combinedMask);

        /// <summary>
        /// 获取年龄信息
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <param name="ageInfo">检测到的年龄信息</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFGetAge(IntPtr pEngine, IntPtr ageInfo);
        /// <summary>
        /// 人脸检测
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <param name="width">图像宽度</param>
        /// <param name="height">图像高度</param>
        /// <param name="format">图像颜色空间</param>
        /// <param name="imgData">图像数据</param>
        /// <param name="detectedFaces">人脸检测结果</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFDetectFaces(IntPtr pEngine, int width, int height, int format, IntPtr imgData, IntPtr detectedFaces);

        /// <summary>
        /// 获取活体信息
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <param name="genderInfo">检测到的性别信息</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFGetLivenessScore(IntPtr pEngine, IntPtr livenessInfo);

        /// <summary>
        /// 激活人脸识别SDK引擎函数
        /// </summary>
        /// <param name="appId">SDK对应的AppID</param>
        /// <param name="sdkKey">SDK对应的SDKKey</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFActivation(string appId, string sdkKey);

        /// <summary>
        /// 获取性别信息
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <param name="genderInfo">检测到的性别信息</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFGetGender(IntPtr pEngine, IntPtr genderInfo);

        /// <summary>
        /// 销毁引擎
        /// </summary>
        /// <param name="pEngine">引擎handle</param>
        /// <returns>调用结果</returns>
        [DllImport("libarcsoft_face_engine.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int ASFUninitEngine(IntPtr pEngine);

        private static object locks = new object();

        /// <summary>
        /// 初始化引擎
        /// </summary>
        /// <param name="pImageEngine"></param>
        /// <returns></returns>
        public static int InitEngine(uint detectMode = DetectionMode.ASF_DETECT_MODE_VIDEO, int detectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT
            , int detectFaceScaleVal = 16, int detectFaceMaxNum = 1, int combinedMask = FaceEngineMask.ASF_FACE_DETECT)
        {
            //uint detectMode = DetectionMode.ASF_DETECT_MODE_VIDEO;
            //检测脸部的角度优先值
            //int detectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT;
            //人脸在图片中所占比例，如果需要调整检测人脸尺寸请修改此值，有效数值为2-32
            //int detectFaceScaleVal = 16;
            //最大需要检测的人脸个数
            //int detectFaceMaxNum = 1;
            //引擎初始化时需要初始化的检测功能组合
            //int combinedMask = FaceEngineMask.ASF_FACE_DETECT;

            int retCode = ASFInitEngine(detectMode, detectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pImageEngine);

            return retCode;
        }

        /// <summary>
        /// 销毁引擎
        /// </summary>
        /// <param name="pEngine"></param>
        /// <returns></returns>
        public static int UninitEngine()
        {
            return ASFUninitEngine(pImageEngine);
        }

        /// <summary>
        /// 激活人脸识别SDK引擎函数
        /// </summary>
        /// <param name="APPID"></param>
        /// <param name="SDKKEY"></param>
        /// <returns></returns>
        private static int Activation(string APPID,string SDKKEY)
        {
            return ASFActivation(APPID, SDKKEY);
        }

        /// <summary>
        /// 人脸检测
        /// </summary>
        /// <param name="imageInfo"></param>
        /// <returns></returns>
        public static ASF_MultiFaceInfo DetectFace( ImageInfo imageInfo)
        {
            ASF_MultiFaceInfo multiFaceInfo = new ASF_MultiFaceInfo();
            IntPtr pMultiFaceInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_MultiFaceInfo>());
            int retCode = ASFDetectFaces(pImageEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo);
            if (retCode != 0)
            {
                Marshal.FreeHGlobal(pMultiFaceInfo);
                return multiFaceInfo;
            }
            multiFaceInfo = Marshal.PtrToStructure<ASF_MultiFaceInfo>(pMultiFaceInfo);
            Marshal.FreeHGlobal(pMultiFaceInfo);
            return multiFaceInfo;
        }


        /// <summary>
        /// 获取图片信息
        /// </summary>
        /// <param name="image">图片</param>
        /// <returns>成功或失败</returns>
        public static ImageInfo ReadBMP(Image image)
        {
            ImageInfo imageInfo = new ImageInfo();

            //将Image转换为Format24bppRgb格式的BMP
            Bitmap bm = new Bitmap(image);
            BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            try
            {
                //位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行
                IntPtr ptr = data.Scan0;

                //定义数组长度
                int soureBitArrayLength = data.Height * Math.Abs(data.Stride);
                byte[] sourceBitArray = new byte[soureBitArrayLength];

                //将bitmap中的内容拷贝到ptr_bgr数组中
                //MemoryUtil.Copy(ptr, sourceBitArray, 0, soureBitArrayLength);
                Marshal.Copy(ptr, sourceBitArray, 0, soureBitArrayLength);

                //填充引用对象字段值
                imageInfo.width = data.Width;
                imageInfo.height = data.Height;
                imageInfo.format = 0x201;

                //获取去除对齐位后度图像数据
                int line = imageInfo.width * 3;
                int pitch = Math.Abs(data.Stride);
                int bgr_len = line * imageInfo.height;
                byte[] destBitArray = new byte[bgr_len];

                /*
                 * 图片像素数据在内存中是按行存储，一般图像库都会有一个内存对齐，在每行像素的末尾位置
                 * 每行的对齐位会使每行多出一个像素空间（三通道如RGB会多出3个字节，四通道RGBA会多出4个字节）
                 * 以下循环目的是去除每行末尾的对齐位，将有效的像素拷贝到新的数组
                 */
                for (int i = 0; i < imageInfo.height; ++i)
                {
                    Array.Copy(sourceBitArray, i * pitch, destBitArray, i * line, line);
                }

                imageInfo.imgData = Marshal.AllocHGlobal(destBitArray.Length);
                Marshal.Copy(destBitArray, 0, imageInfo.imgData, destBitArray.Length);

                return imageInfo;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                bm.UnlockBits(data);
            }

            return null;
        }

        /// <summary>
        /// 获取活体信息
        /// </summary>
        /// <param name="pEngine"></param>
        /// <param name="imageInfo"></param>
        /// <param name="multiFaceInfo"></param>
        /// <returns></returns>
        public static int GetLivenessScore(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo)
        {

            IntPtr pMultiFaceInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_MultiFaceInfo>());
            Marshal.StructureToPtr(multiFaceInfo, pMultiFaceInfo, false);
            IntPtr pLivenessInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_LivenessInfo>());

            int retCode = ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_LivenessScore);
            if (retCode == 0)
            {
                retCode = ASFGetLivenessScore(pEngine, pLivenessInfo);
                Console.WriteLine("Get Gender Result:" + retCode);
                ASF_LivenessInfo info = Marshal.PtrToStructure<ASF_LivenessInfo>(pLivenessInfo);

                //释放内存
                Marshal.FreeHGlobal(pMultiFaceInfo);
                Marshal.FreeHGlobal(pLivenessInfo);

                if (retCode == 0 && info.num > 0)
                {
                    int isLive = Marshal.PtrToStructure<int>(info.isLive);
                    if (isLive == 1)
                    {
                        return 1;
                    }

                }
                
            }
            Marshal.FreeHGlobal(pMultiFaceInfo);
            Marshal.FreeHGlobal(pLivenessInfo);
            return 0;
        }


        /// <summary>
        /// 性别检测
        /// </summary>
        /// <param name="pEngine">引擎Handle</param>
        /// <param name="imageInfo">图像数据</param>
        /// <param name="multiFaceInfo">人脸检测结果</param>
        /// <returns>保存性别检测结果结构体</returns>
        public static ASF_GenderInfo GenderEstimation(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out int retCode)
        {
            retCode = -1;
            IntPtr pMultiFaceInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_MultiFaceInfo>());
            Marshal.StructureToPtr(multiFaceInfo, pMultiFaceInfo, false);

            if (multiFaceInfo.faceNum == 0)
            {
                return new ASF_GenderInfo();
            }

            //人脸信息处理
            retCode = ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_GENDER);
            if (retCode == 0)
            {
                //获取性别信息
                IntPtr pGenderInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_GenderInfo>());
                retCode = ASFGetGender(pEngine, pGenderInfo);
                Console.WriteLine("Get Gender Result:" + retCode);
                ASF_GenderInfo genderInfo = Marshal.PtrToStructure<ASF_GenderInfo>(pGenderInfo);

                //释放内存
                Marshal.FreeHGlobal(pMultiFaceInfo);
                Marshal.FreeHGlobal(pGenderInfo);

                return genderInfo;
            }
            else
            {
                return new ASF_GenderInfo();
            }
        }

        /// <summary>
        /// 年龄检测
        /// </summary>
        /// <param name="pEngine">引擎Handle</param>
        /// <param name="imageInfo">图像数据</param>
        /// <param name="multiFaceInfo">人脸检测结果</param>
        /// <returns>年龄检测结构体</returns>
        public static ASF_AgeInfo AgeEstimation(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out int retCode)
        {
            retCode = -1;
            IntPtr pMultiFaceInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_MultiFaceInfo>());
            Marshal.StructureToPtr(multiFaceInfo, pMultiFaceInfo, false);

            if (multiFaceInfo.faceNum == 0)
            {
                return new ASF_AgeInfo();
            }

            //人脸信息处理
            retCode = ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_AGE);
            if (retCode == 0)
            {
                //获取年龄信息
                IntPtr pAgeInfo = Marshal.AllocHGlobal(Marshal.SizeOf<ASF_AgeInfo>());
                retCode = ASFGetAge(pEngine, pAgeInfo);
                Console.WriteLine("Get Age Result:" + retCode);
                ASF_AgeInfo ageInfo = Marshal.PtrToStructure<ASF_AgeInfo>(pAgeInfo);

                //释放内存
                Marshal.FreeHGlobal(pMultiFaceInfo);
                Marshal.FreeHGlobal(pAgeInfo);
                return ageInfo;
            }
            else
            {
                return new ASF_AgeInfo();
            }
        }

        /// <summary>
        /// 按指定宽高缩放图片
        /// </summary>
        /// <param name="image">原图片</param>
        /// <param name="dstWidth">目标图片宽</param>
        /// <param name="dstHeight">目标图片高</param>
        /// <returns></returns>
        public static Image ScaleImage(Image image, int dstWidth, int dstHeight)
        {
            Graphics g = null;
            try
            {
                //按比例缩放           
                float scaleRate = getWidthAndHeight(image.Width, image.Height, dstWidth, dstHeight);
                int width = (int)(image.Width * scaleRate);
                int height = (int)(image.Height * scaleRate);

                //将宽度调整为4的整数倍
                if (width % 4 != 0)
                {
                    width = width - width % 4;
                }

                Bitmap destBitmap = new Bitmap(width, height);
                g = Graphics.FromImage(destBitmap);
                g.Clear(Color.Transparent);

                //设置画布的描绘质量         
                g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.DrawImage(image, new Rectangle((width - width) / 2, (height - height) / 2, width, height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);

                //设置压缩质量     
                EncoderParameters encoderParams = new EncoderParameters();
                long[] quality = new long[1];
                quality[0] = 100;
                EncoderParameter encoderParam = new EncoderParameter(Encoder.Quality, quality);
                encoderParams.Param[0] = encoderParam;

                return destBitmap;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                if (g != null)
                {
                    g.Dispose();
                }
            }

            return null;
        }

        /// <summary>
        /// 获取图片缩放比例
        /// </summary>
        /// <param name="oldWidth">原图片宽</param>
        /// <param name="oldHeigt">原图片高</param>
        /// <param name="newWidth">目标图片宽</param>
        /// <param name="newHeight">目标图片高</param>
        /// <returns></returns>
        public static float getWidthAndHeight(int oldWidth, int oldHeigt, int newWidth, int newHeight)
        {
            //按比例缩放           
            float scaleRate = 0.0f;
            if (oldWidth >= newWidth && oldHeigt >= newHeight)
            {
                int widthDis = oldWidth - newWidth;
                int heightDis = oldHeigt - newHeight;
                if (widthDis > heightDis)
                {
                    scaleRate = newWidth * 1f / oldWidth;
                }
                else
                {
                    scaleRate = newHeight * 1f / oldHeigt;
                }
            }
            else if (oldWidth >= newWidth && oldHeigt < newHeight)
            {
                scaleRate = newWidth * 1f / oldWidth;
            }
            else if (oldWidth < newWidth && oldHeigt >= newHeight)
            {
                scaleRate = newHeight * 1f / oldHeigt;
            }
            else
            {
                int widthDis = newWidth - oldWidth;
                int heightDis = newHeight - oldHeigt;
                if (widthDis > heightDis)
                {
                    scaleRate = newHeight * 1f / oldHeigt;
                }
                else
                {
                    scaleRate = newWidth * 1f / oldWidth;
                }
            }
            return scaleRate;
        }
    }
}
